/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h"
#include "plugins/ecmascript/runtime/ecma_string.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_array.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/js_hclass.h"
#include "plugins/ecmascript/runtime/js_map_iterator.h"
#include "plugins/ecmascript/runtime/js_object-inl.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/js_weak_container.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/tagged_array-inl.h"
#include "plugins/ecmascript/tests/runtime/common/test_helper.h"
#include "utils/bit_utils.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;
// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript::builtins;

namespace ark::test {
using JSWeakMap = ecmascript::JSWeakMap;

class BuiltinsWeakMapTest : public testing::Test {
public:
    static void SetUpTestCase()
    {
        GTEST_LOG_(INFO) << "SetUpTestCase";
    }

    static void TearDownTestCase()
    {
        GTEST_LOG_(INFO) << "TearDownCase";
    }

    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {nullptr};

private:
    PandaVM *instance_ {nullptr};
    EcmaHandleScope *scope_ {nullptr};
};

static JSObject *JSObjectTestCreate(JSThread *thread)
{
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    JSHandle<GlobalEnv> globalEnv = ecmaVm->GetGlobalEnv();
    JSHandle<JSTaggedValue> jsFunc = globalEnv->GetObjectFunction();
    return *factory->NewJSObjectByConstructor(JSHandle<JSFunction>(jsFunc), jsFunc);
}

JSWeakMap *CreateBuiltinsWeakMap(JSThread *thread)
{
    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSFunction> newTarget(env->GetWeakMapFunction());
    // 4 : test case
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, newTarget.GetTaggedValue(), 4);
    ecmaRuntimeCallInfo->SetFunction(newTarget.GetTaggedValue());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = weak_map::WeakMapConstructor(ecmaRuntimeCallInfo.get());

    EXPECT_TRUE(result.IsECMAObject());
    return JSWeakMap::Cast(reinterpret_cast<TaggedObject *>(result.GetRawData()));
}

// new Map("abrupt").toString()
TEST_F(BuiltinsWeakMapTest, CreateAndGetSize)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSFunction> newTarget(env->GetWeakMapFunction());
    JSHandle<JSWeakMap> map(thread_, CreateBuiltinsWeakMap(thread_));

    JSHandle<TaggedArray> array(factory->NewTaggedArray(1));
    JSHandle<TaggedArray> internalArray(factory->NewTaggedArray(2));
    JSTaggedValue value(JSObjectTestCreate(thread_));
    internalArray->Set(thread_, 0, value);
    internalArray->Set(thread_, 1, JSTaggedValue(0));
    auto result = JSArray::CreateArrayFromList(thread_, internalArray);
    array->Set(thread_, 0, result);

    JSHandle<JSArray> values = JSArray::CreateArrayFromList(thread_, array);

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo->SetFunction(newTarget.GetTaggedValue());
    ecmaRuntimeCallInfo->SetThis(map.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(0, values.GetTaggedValue());
    ecmaRuntimeCallInfo->SetNewTarget(newTarget.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());

    JSTaggedValue result1 = weak_map::WeakMapConstructor(ecmaRuntimeCallInfo.get());
    JSHandle<JSWeakMap> weakMap(thread_, JSWeakMap::Cast(reinterpret_cast<TaggedObject *>(result1.GetRawData())));
    EXPECT_EQ(weakMap->GetSize(), 1);
}

TEST_F(BuiltinsWeakMapTest, SetAndHas)
{
    // create jsWeakMap
    JSHandle<JSWeakMap> weakMap(thread_, CreateBuiltinsWeakMap(thread_));
    JSHandle<JSTaggedValue> key(thread_, JSObjectTestCreate(thread_));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 8);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(weakMap.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(0, key.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(1, JSTaggedValue(static_cast<int32_t>(1)));

    {
        [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
        JSTaggedValue result1 = weak_map::proto::Has(ecmaRuntimeCallInfo.get());

        EXPECT_EQ(result1.GetRawData(), JSTaggedValue::False().GetRawData());
    }

    // test Set()
    JSTaggedValue result2 = weak_map::proto::Set(ecmaRuntimeCallInfo.get());
    EXPECT_TRUE(result2.IsECMAObject());
    JSWeakMap *jsWeakMap = JSWeakMap::Cast(reinterpret_cast<TaggedObject *>(result2.GetRawData()));
    EXPECT_EQ(jsWeakMap->GetSize(), 1);

    // test Has()
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 8);
    ecmaRuntimeCallInfo1->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo1->SetCallArg(0, key.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(1, JSTaggedValue(static_cast<int32_t>(1)));
    ecmaRuntimeCallInfo1->SetThis(JSTaggedValue(jsWeakMap));
    {
        [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
        JSTaggedValue result3 = weak_map::proto::Has(ecmaRuntimeCallInfo1.get());

        EXPECT_EQ(result3.GetRawData(), JSTaggedValue::True().GetRawData());
    }
}

TEST_F(BuiltinsWeakMapTest, DeleteAndRemove)
{
    // create jsWeakMap
    JSHandle<JSWeakMap> weakMap(thread_, CreateBuiltinsWeakMap(thread_));

    // add 40 keys
    JSTaggedValue lastKey(JSTaggedValue::Undefined());
    // NOLINTNEXTLINE(readability-magic-numbers)
    for (int i = 0; i < 40; i++) {
        JSHandle<JSTaggedValue> key(thread_, JSObjectTestCreate(thread_));
        auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 8);
        ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
        ecmaRuntimeCallInfo->SetThis(weakMap.GetTaggedValue());
        ecmaRuntimeCallInfo->SetCallArg(0, key.GetTaggedValue());
        ecmaRuntimeCallInfo->SetCallArg(1, JSTaggedValue(static_cast<int32_t>(i)));

        [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
        JSTaggedValue result1 = weak_map::proto::Set(ecmaRuntimeCallInfo.get());

        EXPECT_TRUE(result1.IsECMAObject());
        JSWeakMap *jsWeakMap = JSWeakMap::Cast(reinterpret_cast<TaggedObject *>(result1.GetRawData()));
        EXPECT_EQ(jsWeakMap->GetSize(), i + 1);
        lastKey = key.GetTaggedValue();
    }

    // whether jsWeakMap has delete lastKey

    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo1->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo1->SetThis(weakMap.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, lastKey);

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result2 = weak_map::proto::Has(ecmaRuntimeCallInfo1.get());

    EXPECT_EQ(result2.GetRawData(), JSTaggedValue::True().GetRawData());

    // delete
    JSTaggedValue result3 = weak_map::proto::Delete(ecmaRuntimeCallInfo1.get());

    EXPECT_EQ(result3.GetRawData(), JSTaggedValue::True().GetRawData());

    // check deleteKey is deleted
    JSTaggedValue result4 = weak_map::proto::Has(ecmaRuntimeCallInfo1.get());

    EXPECT_EQ(result4.GetRawData(), JSTaggedValue::False().GetRawData());
}
}  // namespace ark::test
